// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:cfg/ir/flow_graph.dart';
import 'package:cfg/ir/instructions.dart';
import 'package:cfg/utils/arena.dart';

/// A single use of the result of the instruction in another instruction.
extension type const Use(ArenaPointer _ptr) {
  // Terminator in the use lists.
  static const Use Null = Use(ArenaPointer.Null);

  // Each use occupies 4 slots:
  // instruction index, definition index, next use, previous use.
  static const int instructionOffset = 0;
  static const int definitionOffset = 1;
  static const int nextOffset = 2;
  static const int previousOffset = 3;

  // Size of each use. Should be a power of 2 for efficiency.
  static const int useSize = 4;

  @pragma("vm:prefer-inline")
  void init(FlowGraph graph, Instruction instr) {
    setInstruction(graph, instr);
    setNext(graph, Use.Null);
    setPrevious(graph, Use.Null);
  }

  @pragma("vm:prefer-inline")
  Instruction getInstruction(FlowGraph graph) =>
      graph.instructions[graph[_ptr + instructionOffset]];

  @pragma("vm:prefer-inline")
  void setInstruction(FlowGraph graph, Instruction value) {
    graph[_ptr + instructionOffset] = value.id;
  }

  @pragma("vm:prefer-inline")
  Definition getDefinition(FlowGraph graph) =>
      graph.instructions[graph[_ptr + definitionOffset]] as Definition;

  @pragma("vm:prefer-inline")
  void setDefinition(FlowGraph graph, Definition value) {
    graph[_ptr + definitionOffset] = value.id;
  }

  @pragma("vm:prefer-inline")
  Use getNext(FlowGraph graph) => Use(ArenaPointer(graph[_ptr + nextOffset]));

  @pragma("vm:prefer-inline")
  void setNext(FlowGraph graph, Use value) {
    graph[_ptr + nextOffset] = value._ptr.toInt();
  }

  @pragma("vm:prefer-inline")
  Use getPrevious(FlowGraph graph) =>
      Use(ArenaPointer(graph[_ptr + previousOffset]));

  @pragma("vm:prefer-inline")
  void setPrevious(FlowGraph graph, Use value) {
    graph[_ptr + previousOffset] = value._ptr.toInt();
  }
}

/// Fixed-size array of uses.
extension type const UsesArray(ArenaPointer _ptr) {
  // Array has a length, followed by elements.
  static const int lengthOffset = 0;
  static const int elementsOffset = 1;

  int getLength(FlowGraph graph) {
    assert(_ptr != ArenaPointer.Null);
    return graph[_ptr + lengthOffset];
  }

  Use at(FlowGraph graph, int index) {
    assert(_ptr != ArenaPointer.Null);
    assert(0 <= index && index < getLength(graph));
    return Use(_ptr + elementsOffset + index * Use.useSize);
  }

  void truncateTo(FlowGraph graph, int newLength) {
    assert(_ptr != ArenaPointer.Null);
    assert((0 <= newLength) && (newLength <= getLength(graph)));
    graph[_ptr + lengthOffset] = newLength;
  }

  static UsesArray allocate(FlowGraph graph, int length) {
    assert(length >= 0);
    final ptr = graph.allocate(elementsOffset + length * Use.useSize);
    graph[ptr + lengthOffset] = length;
    return UsesArray(ptr);
  }
}

class _UsesIterator implements Iterator<Use> {
  final FlowGraph graph;
  Use _current = Use.Null;
  Use _next;

  _UsesIterator(this.graph, this._next);

  @override
  bool moveNext() {
    _current = _next;
    _next = (_current != Use.Null) ? _current.getNext(graph) : Use.Null;
    return (_current != Use.Null);
  }

  @override
  Use get current => _current;
}

class UsesIterable extends Iterable<Use> {
  final FlowGraph graph;
  Use _first;

  UsesIterable(this.graph, this._first);
  UsesIterable.empty(this.graph) : _first = Use.Null;

  @override
  Iterator<Use> get iterator => _UsesIterator(graph, _first);
}
